/**
 * Copyright (C) 2009 eXo Platform SAS.
 * 
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 * 
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

/**
 * Manages the main navigation menu on the portal
 */

(function($, base, uiPopup) {	
	var uiPopupMenu = {
	
	  // Elements that must be hidden
	  elementsToHide : [],
	  // Elements that must be kept visible
	  currentVisibleContainers : [],
	
	  /**
	   * initialize UIPopupMenu
	   * 
	   * @param {Object,
	   *          String} popupMenu popup object
	   * @param {Object}
	   *          container
	   */
	  init : function(popupMenu, container) {
	    this.superClass = uiPopup;
	    this.superClass.init(popupMenu, container.id);
	  },
	  /**
	   * Set position to a popup
	   * 
	   * @param {Object}
	   *          popupMenu
	   * @param {Number}
	   *          x x axis
	   * @param {Number}
	   *          y y axis
	   * @param {boolean}
	   *          isRTL right to left flag
	   */
	  setPosition : function(popupMenu, x, y, isRTL) {
	    this.superClass.setPosition(popupMenu, x, y, isRTL);
	  },
	  /**
	   * Set size to a popup
	   * 
	   * @param {Object}
	   *          popupMenu
	   * @param {Number}
	   *          w width
	   * @param {Number}
	   *          h height
	   */
	  setSize : function(popup, w, h) {
	    this.superClass.setSize(popupMenu, w, h);
	  },
	
	  pushVisibleContainer : function(containerId) {
	    uiPopupMenu.currentVisibleContainers.push(containerId);
	  },
	
	  popVisibleContainer : function() {
	    uiPopupMenu.currentVisibleContainers.pop();
	  },
	
	  pushHiddenContainer : function(containerId) {
	    uiPopupMenu.elementsToHide.push(containerId);
	  },
	  /**
	   * Function called when an element (or more) must be hidden Sets a timeout to
	   * time (or 100ms by default) after which the elements in elementsToHide will
	   * be hidden
	   */
	  setCloseTimeout : function(time) {
	    if (!time)
	      time = 100;
	    setTimeout(uiPopupMenu.doOnMenuItemOut, time);
	  },
	  /**
	   * Adds an onCLick event to link elements If they are http links, changes the
	   * url in the browser If they are javascript links, executes the javascript
	   */
	  createLink : function(menuItem, link) {
	    if (link && link.href) {
	      menuItem.onclick = function(e) {
	        if (link.href.substr(0, 7) == "http://")
	          window.location.href = link.href;
	        else
	          eval(link.href);
	        if (!e)
	          e = window.event;
	        if (e.stopPropagation)
	          e.stopPropagation();
	        e.cancelBubble = true;
	        return false;
	      }
	    }
	  },
	
	  /**
	   * The callback function called when timeout is finished Hides the submenus
	   * that are no longer pointed at
	   */
	  doOnMenuItemOut : function() {
	    while (uiPopupMenu.elementsToHide.length > 0) {
	      var container = document
	          .getElementById(uiPopupMenu.elementsToHide.shift());
	      if (container) {
	        /*
	         * It can happen that a submenu appears in both the "to-hide" list and
	         * the "keep-visible" list This happens because when the mouse moves
	         * from the border of an item to the content of this item, a mouseOut
	         * Event is fired and the item submenu is added to the "to-hide" list
	         * while it remains in the "keep-visible" list. Here, we check that the
	         * item submenu doesn't appear in the "keep-visible" list before we hide
	         * it
	         */
	        if ($.inArray(container.id, uiPopupMenu.currentVisibleContainers) === -1) {
	          uiPopupMenu.hide(container);
	        }
	      }
	    }
	  },
	
	  showMenuItemContainer : function(menuItemContainer, x, y) {
	    /*
	     * menuItemContainer.style.display = "block" ; var x = menuItem.offsetWidth +
	     * menuItem.offsetLeft; var y = menuItem.offsetTop; var rootX =
	     * eXo.core.Browser.findPosX(menuItem); var rootY =
	     * eXo.core.Browser.findPosY(menuItem); if (x +
	     * menuItemContainer.offsetWidth + rootX >
	     * eXo.core.Browser.getBrowserWidth()) { x -= (menuItemContainer.offsetWidth +
	     * menuItem.offsetWidth); } if (y + menuItemContainer.offsetHeight + rootY >
	     * eXo.core.Browser.getBrowserHeight()) { y -=
	     * (menuItemContainer.offsetHeight - menuItem.offsetHeight); }
	     */
	    this.superClass.setPosition(menuItemContainer, x, y);
	  },
	  /**
	   * Change object to hidden state
	   * 
	   * @param {Object}
	   *          object to hide
	   */
	  hide : function(object) {
	    if (typeof (object) == "string")
	      object = document.getElementById(object);
            // 508 compatibility
	    // object.style.display = "none";
	    // object.style.visibility = "hidden";
            if (object.className.indexOf("skipHidden") === -1) {
              object.className = object.className + " skipHidden";
            }
	  },
	  /**
	   * Change object to visibility state
	   * 
	   * @param {Object}
	   *          object to hide
	   */
	  show : function(object) {
		this.superClass.show(object);
            // 508 compatibility    
	    // object.style.visibility = "";
            if (object.className.indexOf("skipHidden") !== -1) {
              object.className = object.className.replace(" skipHidden", "");
            }
	  }
	};
	
	var ScrollManager = function(id) {
		if (typeof (id) == "string") id = document.getElementById(id);
		this.mainContainer = id; // The HTML DOM element that contains the tabs, the arrows, etc	
		this.elements = new Array(); // the array containing the elements
		this.firstVisibleIndex = 0; // the index in the array of the first visible element
		this.lastVisibleIndex = -1; // the index in the array of the last visible element
		this.axis = 0; // horizontal scroll : 0 , vertical scroll : 1
		this.currDirection = null; // the direction of the current scroll; left or up scroll : 0, right or down scroll : 1
		this.callback = null; // callback function when a scroll is done
		this.leftArrow = null; // the left arrow dom node
		this.rightArrow = null; // the right arrow dom node
		this.arrowsContainer = null // The HTML DOM element that contains the arrows
		var scroll = this;
		this.refresh = setTimeout(function() {scroll.checkResize()}, 700);
	};
	
	/**
	 * Initializes the scroll manager, with some default parameters
	 */
	ScrollManager.prototype.init = function() {
		this.maxSpace = 0;
		this.firstVisibleIndex = 0;
		this.lastVisibleIndex = -1;
		
		if(!this.arrowsContainer)  {
			// Adds the tab elements to the manager
			var arrowsContainer = $(this.mainContainer).find(".ScrollButtons");
			if (arrowsContainer.length) {
				this.arrowsContainer = arrowsContainer[0];			
				// Configures the arrow buttons
				var arrowButtons = arrowsContainer.find("a");
				if (arrowButtons.length == 2) {
					this.initArrowButton(arrowButtons[0], "left", "ScrollLeftButton", "HighlightScrollLeftButton", "DisableScrollLeftButton");
					this.initArrowButton(arrowButtons[1], "right", "ScrollRightButton", "HighlightScrollRightButton", "DisableScrollRightButton");
				}					
			}
		}
		
		// Hides the arrows by default
		if(this.arrowsContainer)  {
			this.arrowsContainer.style.display =  "none";
			this.arrowsContainer.space = null;
		}			
	};
	
	/**
	 * Loads the tabs in the scroll manager, depending on their css class
	 * If clean is true, calls cleanElements to remove the space property of each element
	 */
	ScrollManager.prototype.loadElements = function(elementClass, clean) {
		if (clean) this.cleanElements();
		this.elements = $(this.mainContainer).find("." + elementClass);
	};
	
	/**
	 * Initializes the arrows with :
	 *  . mouse listeners
	 *  . css class and other parameters
	 */
	ScrollManager.prototype.initArrowButton = function(arrow, dir, normalClass, overClass, disabledClass) {
		if (arrow) {
			arrow = $(arrow);
			arrow[0].direction = dir; // "left" or "right" (up or down)
			arrow[0].overClass = overClass; // the css class for mouse over event
			arrow[0].disabledClass = disabledClass; // the css class for a disabled arrow
			arrow[0].styleClass = normalClass; // the css class for an enabled arrow, in the normal state
			arrow[0].scrollMgr = this; // an easy access to the scroll manager
			arrow.on("mouseover", this.mouseOverArrow);
			arrow.on("mouseout", this.mouseOutArrow);
			arrow.on("click", this.scroll);
			if (dir == "left") this.leftArrow = arrow[0];
			else if (dir == "right") this.rightArrow = arrow[0];
		}
	};
	
	/**
	 * Disables or enables the arrow
	 */
	ScrollManager.prototype.enableArrow = function(arrow, enabled) {
		if (arrow && !enabled) { // disables the arrow
			arrow.className = arrow.disabledClass;
		} else if (arrow && enabled) { // enables the arrow
			arrow.className = arrow.styleClass;
		}
	};
	/**
	 * Sets the mouse over css style of the arrow (this)
	 * only if it is enabled
	 */
	ScrollManager.prototype.mouseOverArrow = function(e) {
		var arrow = this;
		if (arrow.className == arrow.styleClass) {
			arrow.className = arrow.overClass;
		}
	};
	/**
	 * Sets the mouse out css style of the arrow (this)
	 * only if it is enabled
	 */
	ScrollManager.prototype.mouseOutArrow = function(e) {
		var arrow = this;
		if (arrow.className == arrow.overClass) {
			arrow.className = arrow.styleClass;
		}
	};
	
	/**
	 * Calculates the available space for the elements, and inits the elements array like this :
	 *  . maxSpace = space of mainContainer - space of arrowsContainer - a margin
	 *  . browses the elements and add their space to elementsSpace, for each element compares elementsSpace with maxSpace
	 *  . if elementsSpace le maxSpace : the current element is set visible, and its index becomes the lastVisibleIndex
	 *  . if elementsSpace gt maxSpace : the current element is set hidden (isVisible = false)
	 * At the end, each visible element has an isVisible property set to true, the other elements are set to false,
	 * the firstVisibleIndex is 0, the lastVisibleIndex is the last element with isVisible to true
	 */
	ScrollManager.prototype.checkAvailableSpace = function() { // in pixels
		if (!this.maxSpace) {
			this.maxSpace = $(this.mainContainer).width() - this.getElementSpace(this.arrowsContainer);
		}
		var elementsSpace = 0, margin = 0;
		var length =  this.elements.length;
		if (!this.currDirection) {
			for (var i = this.firstVisibleIndex; i < length; i++) {
				elementsSpace += this.getElementSpace(this.elements[i]);
				if (elementsSpace  < this.maxSpace) {
					this.elements[i].isVisible = true;
					this.lastVisibleIndex = i;
				} else {
					this.elements[i].isVisible = false;
				}
			}
		} else {
			for (var i = this.lastVisibleIndex; i >= 0; i--) {
				elementsSpace += this.getElementSpace(this.elements[i]);
				if (elementsSpace  < this.maxSpace) {
					this.elements[i].isVisible = true;
					this.firstVisibleIndex = i;
				} else {
					this.elements[i].isVisible = false;
				}
			}
		}
	};
	
	/**
	 * Calculates the space of the element passed in parameter
	 * The calcul uses : (horizontal tabs | vertical tabs)
	 *  . offsetWidth | offsetHeight
	 *  . marginLeft and marginRight | marginTop and marginBottom
	 *  . the space of the decorator associated with this element, if any
	 * If the element is not rendered (display none), renders it, makes the calcul, and hides it again
	 * The value of the space is stored in a property space of the element. In the function is called on
	 * the same element again, this value is returned directly to avoid another calcul
	 * To remove this value, use the cleanElements function, or set space to null manually
	 */
	ScrollManager.prototype.getElementSpace = function(element) {
		if (element && element.space) { return element.space; }
		var elementSpace = 0;
		if (element) {
			if (this.axis == 0) { // horizontal tabs
				elementSpace += $(element).outerWidth(true);
				// decorator is another element that is linked to the current element (e.g. a separator bar)
				if (element.decorator) elementSpace += this.getElementSpace(element.decorator);
			} else if (this.axis == 1) { // vertical tabs
				elementSpace += $(element).outerHeigth(true);
				if (element.decorator) elementSpace += this.getElementSpace(element.decorator);
			}
			// Store the calculated value for faster return on next calls. To recalculate, set element.space to null.
			element.space = elementSpace;
		}
		return elementSpace;
	};
	
	/**
	 * Clean the elements of the array : set the space property to null
	 */
	ScrollManager.prototype.cleanElements = function() {
		for (var i = 0; i < this.elements.length; i++) {
			this.elements[i].space = null;
			if (this.elements[i].decorator) this.elements[i].decorator.space = null;
		}
	};
	
	/**
	 * Function called when an arrow is clicked. Shows an additionnal element and calls the 
	 * appropriate scroll function (left or right). Works like this :
	 *  . shows the otherHiddenElements again
	 *  . moves the firstVisibleIndex or lastVisibleIndex to the new index
	 *  . clear the otherHiddenElements array
	 *  . calls the appropriate scroll function (left or right)
	 */
	ScrollManager.prototype.scroll = function(e) {
		var src = this;
		if (src.className !== src.disableClass) {
			if (src.direction == "left") src.scrollMgr.scrollLeft();
			else if (src.direction == "right") src.scrollMgr.scrollRight();		
		}
		return false;
	};
	
	ScrollManager.prototype.scrollLeft = function() { // Same for scrollUp
		if (this.firstVisibleIndex > 0) {
			this.currDirection = 0;		
			this.firstVisibleIndex--;		
			this.renderElements();
		}
	};
	
	ScrollManager.prototype.scrollUp = function() {
		if (this.scrollMgr) this.scrollMgr.scrollLeft();
	};
	/**
	 * Scrolls right (or down) :
	 *  . sets the current first visible element hidden
	 *  . increments firstVisibleIndex
	 *  . increments lastVisibleIndex
	 *  . set the new last visible element to visible
	 * Simulates a move to the right of the tabs
	 */
	ScrollManager.prototype.scrollRight = function() { // Same for scrollDown
		if (this.lastVisibleIndex < this.elements.length-1) {
			this.currDirection = 1;		
			this.lastVisibleIndex++;				
			this.renderElements();
		}
	};
	
	ScrollManager.prototype.scrollDown = function() {
		if (this.scrollMgr) this.scrollMgr.scrollRight();
	};
	
	ScrollManager.prototype.renderElements = function() {
		this.checkAvailableSpace();
		
		for (var i = 0; i < this.elements.length; i++) {
			if (this.elements[i].isVisible) { // if the element should be rendered...
				this.elements[i].style.display = "block";
			} else { // if the element must not be rendered...
				this.elements[i].style.display = "none";
				this.arrowsContainer.style.display = "block";
			}
		}
		if (this.arrowsContainer.style.display == "block") {
			this.renderArrows();
		}
		
		if (typeof(this.callback) == "function") this.callback();
	};
	
	/**
	 * Renders the arrows. If we reach the end of the tabs, this end arrow is disabled
	 */
	ScrollManager.prototype.renderArrows = function() {
		// Enables/Disables the arrow buttons depending on the elements to show
		if (this.firstVisibleIndex == 0) this.enableArrow(this.leftArrow, false);
		else this.enableArrow(this.leftArrow, true);
		
		if (this.lastVisibleIndex == this.elements.length-1) this.enableArrow(this.rightArrow, false);
		else this.enableArrow(this.rightArrow, true);
	};
	
	/**
	 * Calculates the space of the elements between indexStart and indexEnd
	 * If these parameters are null, calculates the space for all the elements of the array
	 * Uses the getElementSpace function
	 */
	ScrollManager.prototype.getElementsSpace = function(indexStart, indexEnd) {
		if (indexStart == null && indexEnd == null) {
			indexStart = 0;
			indexEnd = this.elements.length-1 ;
		}
		var elementsSpace = 0;
		if (indexStart >= 0 && indexEnd <= this.elements.length-1) {
			for (var i = indexStart; i <= indexEnd; i++) {
				elementsSpace += this.getElementSpace(this.elements[i]);
			}
		}
		return elementsSpace;
	};
	
	ScrollManager.prototype.checkResize = function() {
		if (this.mainContainer) {
			var tmp = $("#" + this.mainContainer.id);
			if (!tmp.length) {
				clearTimeout(this.refresh);
				return;
			}
			this.mainContainer = tmp[0];
			this.mainContainer.space = null; 
			this.arrowsContainer.space = null;
			var curr = $(this.mainContainer).width() - this.getElementSpace(this.arrowsContainer);
			if (this.maxSpace && this.maxSpace !== curr) {
				var mgrParent = tmp.closest(".UIWindow");
				// if the tabs exist on the page
				// in desktop mode, checks that the UIWindow containing the tabs is
				// visible (display block) 
				if (mgrParent.length == 0 || mgrParent.css("display") == "block") {				
					this.init();
					this.renderElements();
				}
			}		
		}
		var scroll = this;
		this.refresh = setTimeout(function() {scroll.checkResize()}, 700);
	};
	
	var portalNavigation = {
	  hideMenuTimeoutIds : {},
	  scrollMgr : null,
	  
	  /**
	   * Sets some parameters :
	   *  . the superClass to eXo.webui.UIPopupMenu
	   *  . the css style classes
	   * and calls the buildMenu function
	   */
	  init : function(popupMenu, container) {
	    this.superClass = uiPopupMenu;
	    this.superClass.init(popupMenu, container);
	    //UIPopup.js will add onclick event that increase z-index
	    popupMenu.onmousedown = null;
	    
	    this.containerStyleClass = "MenuItemContainer";
	    this.tabStyleClass = "MenuItem";
	
	    this.buildMenu(popupMenu);
	  },
	  
	  /**
	   * Calls the init function when the page loads
	   */
	  onLoad : function(baseId) {    
		var uiNavPortlet = $("#" + baseId);
		if(uiNavPortlet.hasClass("UIHorizontalTabs")) portalNavigation.init(uiNavPortlet[0], uiNavPortlet[0]);
		  
		if (baseId === "UIHorizontalNavigation") {
			$(".UIHorizontalNavigation").slice(1).each(function() {$(this).hide();});
		}
	  },
	  
	  /**
	   * Builds the menu and the submenus
	   * Configures each menu item :
	   *  . sets onmouseover and onmouseout to call setTabStyle
	   *  . sets the width of the item
	   * Checks if a submenu exists, if yes, set some parameters :
	   *  . sets onclick on the item to call toggleSubMenu
	   *  . sets the width and min-width of the sub menu container
	   * For each sub menu item :
	   *  . set onmouseover to onMenuItemOver and onmouseout to onMenuItemOut
	   *  . adds onclick event if the item contains a link, so a click on this item will call the link
	   */
	  buildMenu : function(popupMenu) {
	    var portalNav = portalNavigation;
	    var topContainer = $(popupMenu);
	
	    // Top menu items
	    topContainer.children(".UITab").each(function()
	    {
	      var tab = $(this);
	
	      var highlightClass = "UITab HighlightNavigationTab";
	      tab.mouseenter(function(event)
	      {
	        portalNav.mouseEnterTab($(this), highlightClass);
                event.target.gtnMouseEntered = true; //we need to have this hack here due to the event ordering on some touch based devices
                                                     //This will prevent menu's from being opened twice the older android browsers.
	      });
	      // For keyboard compatibility - menu accessible via TAB key
	      tab.focusin(function()
	      {
	        portalNav.mouseEnterTab($(this), highlightClass);
	      });
	      
	      
	      var actualClass = tab.attr("class");
	      tab.mouseleave(function()
	      {
	        portalNav.mouseLeaveTab($(this), actualClass);
	      });
	      // For keyboard compatibility - menu accessible via TAB key
	      tab.focusout(function()
	      {
	        portalNav.mouseLeaveTab($(this), actualClass);
	      });
	      
              tab.click(function(event)
              {
                portalNav.clickTab(event, $(this), highlightClass, actualClass);
                // Add back the mouseenter here which was removed on a touchstart. To support devices which support both
                // touch and mouse events.
                $(this).on("mouseenter", function() 
                {
                  portalNav.mouseEnterTab($(this), highlightClass);
                  event.target.gtnMouseEntered = true;
                });
              }); 
        
              tab.on("touchstart", function(event)
              {
                /* Need to remove mouseenter event listener here. On some touch based devices, the first
                 * click will cause a virtual mouse to enter at that location, firing off a mouseenter event 
                 * to occur before a click event.
                 * What happens in this case is that the mouse enter enter causes the menu to open, but then the
                 * click toggles it closed again.
                 */
                 $(this).off("mouseenter");
              });
            
	      tab.find("." + portalNav.containerStyleClass).first().css("minWidth", tab.width());
	    });

            // If a menu item has a submenu, then the first click should open the submenu and not
            // follow the link. The next click should follow the link. To support touch based devices.
            topContainer.find(".UITab .MenuItem > .MenuItemContainer").each(function()
            {

                $(this).parent(".MenuItem").on("mouseover", function(event)
                {
                  event.target.gtnMouseOver = true;
                });

                $(this).parent(".MenuItem").on("touchstart.gtnMobile", function(event)
                {
                  $(this).off("mouseenter mouseover");
                  $(this).parents(".MenuItem").off("click.gtnMobile");
                  $(this).parents(".MenuItem").off("touchstart.gtnMobile");
                  //Note: due to mobile safari not propagating events if a content change occurs in the dom
                  //we cannot use the one method here and we need to manually check if the menu is open or not.
                  if ($(this).children(".MenuItemContainer").css("display") != "block" || $(this).children(".MenuItemContainer").hasClass("skipHidden") || event.target.gtnMouseOver)
                  {
                    $(this).on("click.gtnMobile", function(event)
                    {
                      event.target.gtnMouseOver = null;
                      return false;
                    });
                  }
                  else
                  {
                    event.target.gtnMouseOver = null;
                    $(this).off("click.gtnMobile");
                  };
                });
            });
	
	    var itemConts = topContainer.find("." + this.containerStyleClass);
	    itemConts.each(function()
	    {
	      if (!this.id)
	      {
	        this.id = eXo.generateId("PortalNavigationContainer");
	      }
	      this.resized = false;
	
	      var jObj = $(this);
	      var items = jObj.find("." + portalNav.tabStyleClass);
	      if (items.length == 0)
	      {
	        jObj.remove();
	      }
	      else
	      {
	        jObj.on({"mouseenter" : portalNav.onMenuItemOver, "mouseleave" : portalNav.onMenuItemOut,
                         // For keyboard compatibility - menu accessible via TAB key
                         "focusin" : portalNav.onMenuItemOver, "focusout" : portalNav.onMenuItemOut,
	            "click" : function(event) {
	              var a = $(event.target);
	              var href = a.attr("href");
	          	  if (!href || (href.indexOf("#") !== -1 && !a.attr("onclick"))) {
	          		  event.stopPropagation();        		  
	          	  } else {
	          		  portalNav.hideMenu(jObj.attr("id"));        		  
	          	  }
	             }}, "." + portalNav.tabStyleClass);
	      }
	    });
	  },
	
	  /**
	   * Method triggered as mouse cursor enter a navigation node showed on navigation tab.
	   *
	   * @param tab
	   * @param newClass
	   */
	  mouseEnterTab : function(tab, newClass)
	  {
	    var portalNav = portalNavigation;
	
	    var getNodeURL = tab.children(".exo.getNodeURL").first().val();
	    var menuItemContainer = tab.find("." + portalNav.containerStyleClass).first();
	    if (getNodeURL && !menuItemContainer.length)
	    {
	      var jsChilds = ajaxAsyncGetRequest(getNodeURL, false)
	      try
	      {
	        var data = $.parseJSON(jsChilds);
	      }
	      catch (e)
	      {
	      }
	      if (!data || !data.length)
	      {
	        return;
	      }
	      tab.append(portalNav.generateContainer(data));
	    }
	    tab.attr("class", newClass);
	
	    menuItemContainer = tab.find("." + portalNav.containerStyleClass).first();
	    if (menuItemContainer.length)
	    {
	      portalNav.cancelHideMenuContainer(menuItemContainer.attr("id"));
	      portalNav.showMenu(tab, menuItemContainer);
	    }
	  },
	
	  /**
	   * Method triggered as mouse cursor leaves a navigation node showed on navigation tab
	   *
	   * @param tab
	   * @param oldClass
	   */
	  mouseLeaveTab : function(tab, oldClass)
	  {
	    var portalNav = portalNavigation;
	
	    tab.attr("class", oldClass);
	    var conts = tab.find("." + portalNav.containerStyleClass);
	    if (conts.length)
	    {
	      portalNav.hideMenuTimeoutIds[conts[0].id] = window.setTimeout(function() {portalNav.hideMenu(conts[0].id); }, 0);
	    }
	  },

          clickTab : function (event, tab, newClass, oldClass)
          {
            var portalNav = portalNavigation;

            
            if (tab.attr("class") == newClass) //the menu is open
            {
              // If we detect that the (virtual) mouse is already over the tab, then don't close it.
              // This is to support some touch based devices where the mouse enter event occurs before touch events
              // (eg the older android 'browser').
              if (! event.target.gtnMouseEntered){
                portalNav.mouseLeaveTab(tab, oldClass);
              }
            }
            else //we don't have a submenu, create it
            {
              portalNav.mouseEnterTab(tab, newClass);
            }
            event.target.gtnMouseEntered = null;
	  },
	
	  /**
	* Shows a submenu
	   * Sets the width of the submenu (the first time it is shown) to fix a bug in IE
	   * Sets the currentOpenedMenu to the menu being opened
	   */
	  showMenu : function(tab, menuItemContainer) {
	    var portalNav = portalNavigation;
	    var browser = base.Browser;
	    portalNav.superClass.pushVisibleContainer(menuItemContainer.attr("id"));
	        
	    menuItemContainer.css({"display" : "block", "position" : "absolute"});
	    var offParent = menuItemContainer.offsetParent();
	    var y = tab.height() + browser.findPosYInContainer(tab[0], offParent[0]);
	    var x = browser.findPosXInContainer(tab[0], offParent[0]) + 2;
	    if(base.I18n.isRT()) {
	    	x = browser.findPosXInContainer(tab[0], offParent[0], true);
	    }
	    portalNav.superClass.setPosition(menuItemContainer[0], x, y, base.I18n.isRT());
	    portalNav.superClass.show(menuItemContainer[0]);
	        
	    menuItemContainer.css("width", menuItemContainer.width() + "px");
	
	    var posXinBrowser = menuItemContainer.offset().left;
	  	if(base.I18n.isLT()) {
			if(posXinBrowser + menuItemContainer.width() >= $(window).width()) {
				x += (tab.width() - menuItemContainer.width()) ;
				menuItemContainer.css("left", x + "px");
			}
	  	} else {
                        //if the x offset if negative, its renders outside of the screen, move the menuItemContainer to the right by this amount
                        if (posXinBrowser < 0)
                        {
                              menuItemContainer.css("right", posXinBrowser + "px");
                        }
		}
	  },
	
	  cancelHideMenuContainer : function(containerId) {
		  var timeout = portalNavigation.hideMenuTimeoutIds[containerId];
		  portalNavigation.hideMenuTimeoutIds[containerId] = null;
	      if (timeout) {
	    	  window.clearTimeout(timeout) ;
	      }
	  },
	  
	  /**
	   * Changes the style of the parent button when a submenu has to be hidden
	   */
	  hideMenu : function(containerId) {
	    var portalNav = portalNavigation;
	    portalNav.hideMenuTimeoutIds[containerId] = null;
	
	    var menuItemContainer = $("#" + containerId);
	    if (menuItemContainer.length) {
	      var id = menuItemContainer.attr("id");
	      portalNav.superClass.pushHiddenContainer(id);
	      portalNav.superClass.popVisibleContainer(id);
	      portalNav.superClass.setCloseTimeout();
	      portalNav.superClass.hide(menuItemContainer[0]);
	    }
	  },
	  
	  /**
	   * When the mouse goes over a menu item (in the main nav menu)
	   * Check if this menu item has a sub menu, if yes, opens it
	   * Changes the style of the button
	   */
	  onMenuItemOver : function() {
	    var menuItem = $(this);
	    var portalNav = portalNavigation;
	    
	    var getNodeURL = menuItem.children(".exo.getNodeURL").first().val();
	    var subContainer = menuItem.find("." + portalNav.containerStyleClass).first();
		    if (getNodeURL && !subContainer.length) {
			    var jsChilds = ajaxAsyncGetRequest(getNodeURL, false);
			  	try {
			  		var data = $.parseJSON(jsChilds);
			  	} catch (e) {
		  	}	
		  	if (!data || !data.length) {
			   menuItem.removeClass("ArrowIcon");
		  	   return;
		    }
		  	menuItem.append(portalNav.generateContainer(data));
	    }
	      
	    subContainer = menuItem.find("." + portalNav.containerStyleClass).first();
	    if (subContainer.length) {
	      portalNav.superClass.pushVisibleContainer(subContainer.attr("id"));
	      portalNav.showMenuItemContainer(menuItem, subContainer) ;
	      if (!subContainer.data("firstTime")) {
	          subContainer.css("width", subContainer.width() + 2 + "px");
	          subContainer.data("firstTime", true);
	      }
	    }
          
            // remove the click function on the subContainer. The functionality will still work as before.
            // Prevents an issue on smaller screened devices when a submenu opens underneath where the screen
            // was clicked causing multiple events to occur, closing the menu immediately.
            subContainer.off("click"); 
	  },
	  
	  /**
	   * Shows a sub menu, uses the methods from superClass (eXo.webui.UIPopupMenu)
	   */
	  showMenuItemContainer : function(menuItem, menuItemContainer) {
	    var x = menuItem.width();
	    var y = menuItem.position().top;
	    this.superClass.show(menuItemContainer[0]);
	    var posRight = $(window).width() - base.Browser.findPosX(menuItem[0], true) ;
	    var rootX = (base.I18n.isLT() ? base.Browser.findPosX(menuItem[0]) : posRight) ;
	    if (x + menuItemContainer.width() + rootX > $(window).width()) {
                if (menuItemContainer.width() > rootX) {
                    if ($(window).width() - x - rootX >  rootX) {
                      x += $(window).width() - rootX -x - menuItemContainer.width(); 
                    } else {
                      x -=  menuItem.width() + rootX;
                    }
                } else {
	                x -= (menuItemContainer.width() + menuItem.width()) ;
                }
	    }
	    this.superClass.setPosition(menuItemContainer[0], x, y, base.I18n.isRT());
	  },
	  
	  /**
	   * When the mouse goes out a menu item from the main nav menu
	   * Checks if this item has a sub menu, if yes calls methods from superClass to hide it
	   */
	  onMenuItemOut : function() {
	    var menuItem = $(this);
	    var portalNav = portalNavigation;
	
	    var subContainer = menuItem.find("." + portalNav.containerStyleClass).first();
	    if (subContainer.length) {
	      var id = subContainer.attr("id");
	      portalNav.superClass.pushHiddenContainer(id);
	      var index = $.inArray(id, portalNav.superClass.currentVisibleContainers);
	      if(index !== -1) {
	      	portalNav.superClass.currentVisibleContainers.splice(index, 1);
	      }
	      portalNav.superClass.setCloseTimeout(200);
	    }
	  },
	  
	  /***** Scroll Management *****/
	  /**
	   * Function called to load the scroll manager that will manage the tabs in the main nav menu
	   *  . Creates the scroll manager
	   *  . Adds the tabs to the scroll manager
	   */
	  loadScroll : function(portalNavId) {
	    var uiNav = portalNavigation;
	    var portalNav = $("#" + portalNavId);
	    if (!portalNav.length) return;
	    
	    // Creates new ScrollManager and initializes it
	    uiNav.scrollMgr = new ScrollManager(portalNav[0]);       
	    uiNav.scrollMgr.loadElements("UITab");
	    
	    // Finish initialization
	    uiNav.scrollMgr.init();
	    uiNav.scrollMgr.renderElements();
	  },
	
	  generateContainer : function(data) {
	   var htmlFrags = "<ul class='" + this.containerStyleClass + "' style='display: none;' id='";
	   htmlFrags += eXo.generateId("PortalNavigationContainer") + "' resized='false'>";
	
	   for (var i = 0; i < data.length; i++) {
		   var node = data[i];
		   var actionLink = node.actionLink ? node.actionLink : "#" + node.label;
		
		   htmlFrags += ("<li class='MenuItem " + (node.hasChild ? "ArrowIcon " : "") + (node.isSelected ? "SelectedItem'" : "NormalItem'"));
		   htmlFrags += ("' title='" + node.label + "'>");
           htmlFrags += ("<input type='hidden' class='exo getNodeURL' value='" + node.getNodeURL + "' />");
		   htmlFrags += ("<a class='ItemIcon " + (node.icon ? node.icon : "DefaultPageIcon") + "'" +
		   "href='" + actionLink + "'>" + (node.label.length > 40 ? node.label.substring(0,37) + "..." : node.label) + "</a>");
		   if (node.childs.length) {
			   htmlFrags += portalNavigation.generateContainer(node.childs);
		   }
		   htmlFrags += "</li>";
	   }
	   htmlFrags += "</ul>";
	   return htmlFrags;
	  }
	};
	
	return {
		ScrollManager : ScrollManager,
		UIPopupMenu : uiPopupMenu,
		UIPortalNavigation : portalNavigation	
	};
})($, base, uiPopup);
